package com.moyuaninfo.myyhikwork.hik;

import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.examples.win32.W32API;
import lombok.extern.slf4j.Slf4j;

import java.awt.*;

/**
 * 海康威视摄像头操作
 *
 * @author QiuXiangBa
 * @date 2020/3/12
 **/
@Slf4j
public class HikSdk {
    private static HCNetSDK hcNetSDK = HCNetSDK.INSTANCE;

    public static Panel panel;

    /** 设备IP地址或是静态域名，字符数不大于128个*/
    private String sDVRIP;

    /** 设备端口号*/
    private short wServerPort;

    /** 用户密码*/
    private String sPassword;

    /** 用户密码登录的用户名*/
    private String sUserName;

    /** -1表示失败，其他值表示返回的用户ID值。该用户ID具有唯一性，后续对设备的操作都需要通过此ID实现*/
    private NativeLong lUserID;

    /** int -1表示失败，其他值作为NET_DVR_StopRealPlay等函数的句柄参数*/
    NativeLong lPreviewHandle;

    /** 初始化SDK状态*/
    public boolean initSdkFlag;

    /** 注释设备状态*/
    public boolean loginSdkFlag;

    /** 设备信息*/
    private HCNetSDK.NET_DVR_DEVICEINFO_V30 lpDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V30();

    /**
     * 构造器
     *
     * @param sDVRIP 设备IP地址或是静态域名，字符数不大于128个
     * @param wServerPort 设备端口号
     * @param sUserName 用户密码
     * @param sPassword 用户密码登录的用户名
     *
     * @author QiuXiangBa
     **/
    public HikSdk(String sDVRIP, short wServerPort, String sUserName, String sPassword) {
        this.sDVRIP = sDVRIP;
        this.wServerPort = wServerPort;
        this.sUserName = sUserName;
        this.sPassword = sPassword;
    }

    public static void main(String[] args){
        HikSdk hikSdk = new HikSdk("192.168.1.102", (short) 8000, "admin", "my123456");
        hikSdk.initSdk();
        hikSdk.loginSdk();
        hikSdk.ptzPresetOther(33, 39, 1);
        hikSdk.logoutSdk();
        hikSdk.cleanUp();
    }


    /**
     * 初始化SDK，调用其他SDK函数的前提
     *
     * @return int 错误码，0表示成功
     *
     * @author QiuXiangBa
     * @date 2020/3/12
     **/
    public int initSdk() {
        // 初始化SDK
        boolean initFlag = hcNetSDK.NET_DVR_Init();

        // 初始化SDK失败
        if (!initFlag) {
            int errorCode = hcNetSDK.NET_DVR_GetLastError();
            log.warn("hikSdk(SDK初始化)-初始化SDK失败! 错误码:" + errorCode);
            return errorCode;
        }

        // 初始化SDK成功
        log.info("hikSdk(SDK初始化)-初始化SDK成功");
        this.initSdkFlag = true;
        return 0;
    }

    /**
     * 实时预览
     *
     * @param iChannelNum 通道号，1~32表示模拟通道1~32，9000系列混合型DVR和NVR等设备的IP通道从33开始
     * @return int 错误码，0表示成功
     *
     * @out lPreviewHandle
     *
     * @author QiuXiangBa
     * @date 2020/3/13
     **/
    public int realPlay_V30(int iChannelNum) {
        // 预览参数
        HCNetSDK.NET_DVR_CLIENTINFO m_strClientInfo = new HCNetSDK.NET_DVR_CLIENTINFO();
        m_strClientInfo.lChannel = new NativeLong(iChannelNum);

        m_strClientInfo.hPlayWnd = new W32API.HWND(Native.getComponentPointer(panel));

        // 启动预览
        lPreviewHandle = hcNetSDK.NET_DVR_RealPlay_V30(lUserID, m_strClientInfo, null, null, false);

        // 预览失败
        if (lPreviewHandle.intValue() == -1) {
            int errorCode = hcNetSDK.NET_DVR_GetLastError();
            log.warn("hikSdk(实时预览)-启动预览失败，错误码：" + errorCode);
            return errorCode;
        }
        // 预览成功
        log.info("hikSdk(实时预览)-启动成功, 预览句柄：" + lPreviewHandle);
        return 0;
    }

    /**
     * 停止预览
     *
     * @in lRealHandle 预览句柄，NET_DVR_RealPlay或者NET_DVR_RealPlay_V30的返回值
     * @return int 错误码，0表示成功
     *
     * @author QiuXiangBa
     * @date 2020/3/13
     **/
    public int stopRealPlay() {
        boolean flag = hcNetSDK.NET_DVR_StopRealPlay(lPreviewHandle);

        // 停止预览成功
        if (flag) {
            log.info("hikSdk(实时预览)-停止预览成功");
            return 0;
        }
        // 停止预览失败
        int errorCode = hcNetSDK.NET_DVR_GetLastError();
        log.warn("hikSdk(实时预览)-停止预览失败，错误码：" + errorCode);

        return errorCode;
    }

    /**
     * 预览时，单帧数据捕获并保存成图片<br/>
     * 注：调用NET_DVR_CapturePicture进行抓图，要求在调用NET_DVR_RealPlay_V40等接口时传入非空的播放句柄（播放库解码显示），否则时接口会返回失败，调用次序错误
     *
     * @in lRealHandle	NET_DVR_RealPlay或NET_DVR_RealPlay_V30的返回值
     * @param sPicFileName	保存图象的文件路径（包括文件名）。路径长度和操作系统有关，sdk不做限制，windows默认路径长度小于等于256字节（包括文件名在内）。
     * @return int 错误码 0表示成功
     *
     * @author QiuXiangBa
     * @date 2020/3/13
     **/
    public int capturePicture(String sPicFileName) {
        // 抓图
        boolean flag = hcNetSDK.NET_DVR_CapturePicture(lPreviewHandle, "C:\\Users\\hpt\\Web Kit\\CaptureFiles\\2020-03-13\\"+ System.currentTimeMillis() + ".bmp");

        // 抓图成功
        if (flag) {
            log.info("hikSdk(实时预览)-预览抓图成功");
            return 0;
        }
        // 抓图失败
        int errorCode = hcNetSDK.NET_DVR_GetLastError();
        log.info("hikSdk(实时预览)-预览抓图失败，错误码：" + errorCode);

        return errorCode;
    }

    /**
     * 捕获数据并存放到指定的文件中
     *
     * @in lRealHandle NET_DVR_RealPlay或NET_DVR_RealPlay_V30的返回值
     * @param sFileName	文件路径指针，包括文件名，例如："D:\\test.mp4"
     * @return int 错误码 0表示成功
     *
     * @author QiuXiangBa
     * @date 2020/3/13
     **/
    public int saveRealData(String sFileName) {
        // 捕获数据
        boolean flag = hcNetSDK.NET_DVR_SaveRealData(lPreviewHandle, sFileName);

        // 捕获数据成功
        if (flag) {
            log.info("hikSdk(实时预览)-捕获数据成功");
            return 0;
        }
        // 捕获数据失败
        int errorCode = hcNetSDK.NET_DVR_GetLastError();
        log.warn("hikSdk(实时预览)-捕获数据失败，错误码：" + errorCode);
        return errorCode;
    }

    /**
     * 停止数据捕获
     *
     * @in lRealHandle	NET_DVR_RealPlay或NET_DVR_RealPlay_V30的返回值
     * @return int 错误码 0表示成功
     *
     * @author QiuXiangBa
     * @date 2020/3/13
     **/
    public int stopSaveRealData() {
        // 停止捕获数据
        boolean flag = hcNetSDK.NET_DVR_StopSaveRealData(lPreviewHandle);

        // 停止捕获数据成功
        if (flag) {
            log.info("hikSdk(实时预览)-停止捕获数据成功");
            return 0;
        }
        // 停止捕获数据失败
        int errorCode = hcNetSDK.NET_DVR_GetLastError();
        log.warn("hikSdk(实时预览)-停止捕获数据失败，错误码：" + errorCode);
        return errorCode;
    }

    /**
     * 释放SDK资源，在程序结束之前调用
     *
     * @return int 错误码 0表示成功
     *
     * @author QiuXiangBa
     * @date 2020/3/12
     **/
    public int cleanUp() {
        if (!this.initSdkFlag) {
            return -1;
        }

        // 释放SDK资源
        boolean cleanup = hcNetSDK.NET_DVR_Cleanup();

        // 释放SDK资源成功
        if (cleanup) {
            log.info("hikSdk(SDK初始化)-释放SDK资源成功");
            return 0;
        }
        // 释放SDK资源失败
        int errorCode = hcNetSDK.NET_DVR_GetLastError();
        log.info("hikSdk(SDK初始化)-释放SDK资源失败, 错误码:" + errorCode);

        return errorCode;
    }

    /**
     * 用户注册设备
     *
     * @out lpDeviceInfo 设备信息
     * @out lUserID 用户id值， -1表示失败
     *
     * @return int 错误码 0表示成功
     *
     * @author QiuXiangBa
     * @date 2020/3/14
     **/
    public int loginSdk() {
        // 用户注册设备
        lUserID = hcNetSDK.NET_DVR_Login_V30(sDVRIP, wServerPort, sUserName, sPassword, lpDeviceInfo);

        // 用户注册设备失败
        if (lUserID.intValue() == -1) {
            int errorCode = hcNetSDK.NET_DVR_GetLastError();
            log.warn("hikSdk(用户注册)-用户注册设备失败,错误码:" + errorCode);
            return errorCode;
        }

        // 用户注册设备成功
        log.info("hikSdk(用户注册)-用户注册设备成功");

        this.loginSdkFlag = true;
        return 0;
    }


    /**
     * 用户注销
     *
     * @return int 错误吗 0表示没有错误
     *
     * @author QiuXiangBa
     * @date 2020/3/12
     **/
    public int logoutSdk() {
        if (!this.loginSdkFlag) {
            return -1;
        }

        //用户注销
        boolean logout = hcNetSDK.NET_DVR_Logout(lUserID);

        // 用户注销成功
        if (logout) {
            log.info("hikSdk(用户注册)-用户注销成功");
            return 0;
        }
        // 用户注销失败
        int errorCode =  hcNetSDK.NET_DVR_GetLastError();
        log.warn("hikSdk(用户注册)-用户注销失败，错误码：" + errorCode);
        return errorCode;
    }

    /**
     * 云台控制操作（不需要启动图像预览）<br/>
     * 注：对云台实施的每一个动作都需要调用该接口两次，分别是开始和停止控制，由接口中的最后一个参数（dwStop）决定
     *
     * @param lChannel	通道号
     * @param dwPTZCommand	云台控制命令, 详情见枚举DwPTZCommandEnum
     * @param dwStop  云台停止动作或开始动作：0－开始，1－停止
     * @return int 错误码 0表示成功
     *
     * @author QiuXiangBa
     * @date 2020/3/12
     **/
    public int hikPtzControlOther(int lChannel, int dwPTZCommand, int dwStop) {
        // 云台控制操作
        boolean ptzControlOtherFlag = hcNetSDK.NET_DVR_PTZControl_Other(lUserID, new NativeLong(lChannel), dwPTZCommand, dwStop);

        // 云台控制操作失败
        if (!ptzControlOtherFlag) {
            int errorCode = hcNetSDK.NET_DVR_GetLastError();
            log.warn("hikSdk(云台控制<-云台基本控制)-云台控制操作失败! 错误码:" + errorCode);
            return errorCode;

        }
        // 云台控制操作成功
        log.info("hikSdk(云台控制<-云台基本控制)-云台控制操作成功");
        return 0;
    }

    /**
     * 带速度的云台控制操作(不用启动图象预览)<br/>
     * 注：对云台实施的每一个动作都需要调用该接口两次，分别是开始和停止控制，由接口中的最后一个参数（dwStop）决定
     *
     * @param lChannel	通道号
     * @param dwPTZCommand 云台控制命令, 详情见枚举DwPTZCommandEnum
     * @param dwStop	云台停止动作或开始动作：0－开始；1－停止
     * @param dwSpeed	云台控制的速度，用户按不同解码器的速度控制值设置。取值范围[1,7]
     * @return int 错误码 0表示成功
     *
     * @author QiuXiangBa
     * @date 2020/3/17
     **/
    public int hikPtzControlWithSpeed(int lChannel, int dwPTZCommand, int dwStop, int dwSpeed) {
        // 云台操作
        boolean flag = hcNetSDK.NET_DVR_PTZControlWithSpeed_Other(lUserID, new NativeLong(lChannel), dwPTZCommand, dwStop, dwSpeed);

        // 操作成功
        if (flag) {
            log.info("hikSdk(云台控制<-云台基本控制)-带速度的云台控制操作成功");
            return 0;
        }
        // 操作失败
        int errorCode = hcNetSDK.NET_DVR_GetLastError();
        log.warn("hikSdk(云台控制<-云台基本控制)-带速度的云台控制操作成功，错误码：" + errorCode);
        return errorCode;
    }

    /**
     * TODO 恢复镜头电机默认位置(DK未找到)
     *
     * @param lChannel	通道号
     * @return boolean TRUE表示成功，FALSE表示失败
     *
     * @author QiuXiangBa
     * @date 2020/3/17
     **/
    public boolean hikResetLens(int lChannel) {
//        boolean flag = hcNetSDK.NET_DVR_ResetLens();
        return false;
    }

    /**
     * 云台预置点操作<br/>
     * 注：与设备之间的云台各项操作的命令都对应于设备与云台之间的控制码，
     * 设备会根据目前设置的解码器种类和解码器地址向云台发送控制码。
     * 如果目前设备上设置的解码器与云台设备的不匹配，需要重新配置设备的解码器。
     * 如果云台设备所需的解码器设备不支持，则无法用该接口控制
     *
     * @param lChannel 通道号
     * @param dwPTZPresetCmd 操作云台预置点命令
     * @param dwPresetIndex 预置点的序号（从1开始），最多支持300个预置点
     * @return int 错误码 0表示成功
     *
     * @in lUserID NET_DVR_Login_V40等登录接口的返回值
     *
     * @author QiuXiangBa
     * @date 2020/3/20
     **/
    public int ptzPresetOther(int lChannel, int dwPTZPresetCmd, int dwPresetIndex) {
        // 云台预置点操作
        boolean flag = hcNetSDK.NET_DVR_PTZPreset_Other(lUserID, new NativeLong(lChannel), dwPTZPresetCmd, dwPresetIndex);

        // 云台预置点操作成功
        if (flag) {
            log.info("hikSdk(云台控制<-云台置点功能)-云台预置点操作成功");
            return 0;
        }
        // 云台预置点操作失败
        int errorCode = hcNetSDK.NET_DVR_GetLastError();
        log.warn("hikSdk(云台控制<-云台置点功能)-云台预置点操作失败，错误码：" + errorCode);
        return errorCode;
    }

    /**
     * 重启设备
     *
     * @return int 错误码 0表示成功
     *
     * @author QiuXiangBa
     * @date 2020/3/12
     **/
    public int hikReboot() {
        // 重启设备
        boolean rebootFlag = hcNetSDK.NET_DVR_RebootDVR(lUserID);

        // 重启失败
        if (!rebootFlag) {
            int errorCode = hcNetSDK.NET_DVR_GetLastError();
            log.warn("hikSdk(设备维护管理 <- 恢复出厂默认和重启)-重启设备失败，错误码:" + errorCode);
            return errorCode;
        }
        // 重启成功
        log.info("hikSdk(设备维护管理 <- 恢复出厂默认和重启)-重启设备成功");
        return 0;
    }

    /**
     * 关闭设备
     *
     * @return int 错误码 0表示成功
     *
     * @author QiuXiangBa
     * @date 2020/3/12
     **/
    public int hikShutDown() {

        // 关闭设备
        boolean shutDownFlag = hcNetSDK.NET_DVR_ShutDownDVR(lUserID);

        // 关闭失败
        if (!shutDownFlag) {
            int errorCode = hcNetSDK.NET_DVR_GetLastError();
            log.warn("hikSdk()-关闭设备失败! 错误码:" + errorCode);
            return errorCode;
        }
        // 关闭成功
        log.info("hikSdk()-关闭设备成功");

        return 0;
    }

    /*************************************************
     函数:      toIPString
     函数描述:  主要是与C中的字符串格式一致,比较字符串时,去"\0"之前的一段字符串
     *************************************************/
    String toIPString(String s) {

        //结构体里的设备IP
        String[] sIP = s.split("\0", 2);
        return sIP[0];
    }
}


